using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ContentOverContainer.Common;
using ContentOverContainer.Common.Converter;
using ContentOverContainer.Converter.Converter;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers.Common;
using Roslyn.Services;
using Roslyn.Services.Formatting;

namespace ContentOverContainer.SimpleSyntax
{
    public class MultiplicityConverter
    {
        private readonly ChangeNodeInfoList m_Replacements = new ChangeNodeInfoList();
        private readonly ReferencesList m_References = new ReferencesList();

        public ISolution Solut { get; set; }

        private MultiplicityConverter(ISolution pSol)
        {
            Solut = pSol;
        }

        public static ISolution Convert(ISolution pSolution)
        {
            var converter = new MultiplicityConverter(pSolution);

            converter.BuildLists();

            converter.ResolveDependencies();

            return converter.ProcessReplacements();
        }

        /// <summary>
        /// Resolves the dependencies by replacing nodes within other node to be replaced.
        /// It works blockwise to cover properties, which consits of two blocks.
        /// </summary>
        /// <author>Harald Binkle</author>
        private void ResolveDependencies()
        {
            var temp = new ChangeNodeInfoList();
            temp.AddRange(m_Replacements);
            foreach (var replacement in temp)
            {
                var oldBlocks = replacement.OrgSyntaxNode.DescendantNodes().Where(x => x is BlockSyntax);//get all (2 in case of property)
                var replaces = new List<ChangeNodeInfo>();
                foreach (var oldBlock in oldBlocks)
                {
                    var blockReplaces = m_Replacements.Where(x => x.OrgSyntaxNode.Ancestors().Any(xo => xo.IsEquivalentTo(oldBlock)));
                    replaces.AddRange(blockReplaces);
                }
                var orgNodes = replaces.Select(x => x.OrgSyntaxNode);
                var newblocks = new List<SyntaxNode>();
                foreach (var oldBlock in oldBlocks)
                {

                    var newBlockReplaces = oldBlock.ReplaceNodes(orgNodes, (orgNode, newerNode) =>
                    {
                        foreach (var repl in replaces)
                        {
                            if (repl.OrgSyntaxNode.IsEquivalentTo(newerNode))
                            {
                                m_Replacements.Remove(repl);
                                replacement.MethodSubstitutionList = repl.MethodSubstitutionList;//keep 
                                return repl.NewSyntaxNode;
                            }
                        }
                        return newerNode;
                    });
                    newblocks.Add(newBlockReplaces);
                }


                for (int i = 0; i < newblocks.Count; i++)
                {
                    var newSyntaxNodeBlocks = replacement.NewSyntaxNode.DescendantNodes().Where(x => x is BlockSyntax).ToList();
                    replacement.NewSyntaxNode = replacement.NewSyntaxNode.ReplaceNode( newSyntaxNodeBlocks[i], newblocks[i]);
                }
            }
        }


        private void BuildLists()
        {
            WalkThroughProjects(new FieldWalker(Solut, m_References, m_Replacements));
            WalkThroughProjects(new PropertyWalker(Solut, m_References, m_Replacements));
            WalkThroughProjects(new MethodWalker(Solut, m_References, m_Replacements));
            WalkThroughProjects(new CastExprWalker(Solut, m_References, m_Replacements));
            ProcessReferencesList();
        }

        private void WalkThroughProjects(BaseWalker walker)
        {
            foreach (var project in Solut.Projects)
            {
                foreach (var document in project.Documents)
                {
                    //get pairs for declarations and find references
                    walker.Document = document;
                    walker.Visit(document.GetSyntaxRoot() as SyntaxNode);
                }
            }
        }

        private void ProcessReferencesList()
        {
            var elementsDone = new ReferencesList();
            foreach (var reference in m_References)
            {
                var convertData = new ConverterContext()
                {
                    MultiplicityInfo = reference.MultiplicityInfo,
                    SemanticModel = reference.Document.GetSemanticModel(),
                    NodeTypeInfo = reference
                };
                ChangeNodeInfo newNodeInfo;
                //special handling for return rule, it's the only targeting a statement instead of an expression
                if (reference.Node is ReturnStatementSyntax)
                {
                    newNodeInfo = ReturnConverter.ConvertReturnExpression((ReturnStatementSyntax)reference.Node, convertData);
                }
                else //expression rules
                {
                    var expr = reference.Node as ExpressionSyntax;
                    if (expr == null || (expr is IdentifierNameSyntax))
                        expr = reference.Node.GetExpression();
                    if (expr == null)
                        continue;
                    newNodeInfo = expr.Convert(convertData);
                    if (newNodeInfo.OrgSyntaxNode == null)
                        if (expr.Parent is InvocationExpressionSyntax)
                            newNodeInfo.OrgSyntaxNode = expr.Parent; //get rid of parameterlist
                        else
                            newNodeInfo.OrgSyntaxNode = expr;
                }
                newNodeInfo.Document = reference.Document;
                m_Replacements.Add(newNodeInfo);
                elementsDone.Add(reference);
            }
            elementsDone.ForEach(elem => m_References.Remove(elem));


        }

        /// <summary>
        /// Processes the replacements by calling ReplaceNodes for each document.
        /// </summary>
        /// <returns></returns>
        /// <author>Harald Binkle</author>
        private ISolution ProcessReplacements()
        {
            var solution = Solut;
            foreach (var project in solution.Projects)
            {
                foreach (var document in project.Documents)
                {
                    var root = document.GetSyntaxRoot();

                    var newroot = root.ReplaceNodes(document, m_Replacements);

                    IFormattingResult formatResult = newroot.Format(FormattingOptions.GetDefaultOptions());
                    newroot = (SyntaxNode)formatResult.GetFormattedRoot();
                    IText newText = newroot.GetText();
                    solution = solution.UpdateDocument(document.Id, newText);
                    if (m_Replacements.Count == 0)
                        return solution;
                }
            }

            return solution;
        }





    }
}
